{
  "cells": [
    {
      "cell_type": "markdown",
      "source": [
        "# <ins>MemTorch Tutorial</ins>\n",
        "## Introduction\n",
        "In this tutorial, you will learn how to use MemTorch to convert Deep Neural Networks (DNNs) to Memristive Deep Neural Networks (MDNNs), and how to simulate non-ideal device characteristics and key peripheral circuitry. MemTorch is a Simulation Framework for Memristive Deep Learning Systems, which integrates directly with the well-known PyTorch Machine Learning (ML) library. MemTorch is formally described in *MemTorch: An Open-source Simulation Framework for Memristive Deep Learning Systems*, which is openly accessible [here](https://arxiv.org/abs/2004.10971).\n",
        "\n",
        "![Overview](https://raw.githubusercontent.com/coreylammie/MemTorch/master/overview.svg)\n"
      ],
      "metadata": {
        "id": "340LIHCnH0dR"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "## 1. Installation\n",
        "MemTorch can be installed from source using `python setup.py install`:\n",
        "\n",
        "```\n",
        "git clone --recursive https://github.com/coreylammie/MemTorch\n",
        "cd MemTorch\n",
        "python setup.py install\n",
        "```\n",
        "\n",
        "or using `pip install .`, as follows:\n",
        "\n",
        "```\n",
        "git clone --recursive https://github.com/coreylammie/MemTorch\n",
        "cd MemTorch\n",
        "pip install .\n",
        "```\n",
        "\n",
        "*If CUDA is `True` in `setup.py`, CUDA Toolkit 10.1 and Microsoft Visual C++ Build Tools are required. If `CUDA` is False in `setup.py`, Microsoft Visual C++ Build Tools are required.*\n",
        "\n",
        "Alternatively, MemTorch can be installed using the *pip* package-management system:\n",
        "\n",
        "```\n",
        "pip install memtorch-cpu # Supports normal operation\n",
        "pip install memtorch # Supports CUDA and normal operation\n",
        "```\n",
        "\n",
        "A complete API is avaliable [here](https://memtorch.readthedocs.io/). \n",
        "\n",
        "MemTorch can be installed using Jupyter notebooks as follows:"
      ],
      "metadata": {
        "id": "yWhuJyazH0dV"
      }
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "source": [
        "# Installation of MemTorch (with CUDA functionality) from source using pip\n",
        "!git clone --recursive https://github.com/coreylammie/MemTorch\n",
        "%cd MemTorch\n",
        "!sed -i 's/CUDA = False/CUDA = True/g' setup.py\n",
        "!pip install ."
      ],
      "outputs": [],
      "metadata": {
        "id": "ZM8XW53KH0dW"
      }
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "source": [
        "# Installation of MemTorch (without CUDA functionality) from source using pip\n",
        "!git clone --recursive https://github.com/coreylammie/MemTorch\n",
        "%cd MemTorch\n",
        "!pip install ."
      ],
      "outputs": [],
      "metadata": {
        "id": "VoBS6Y0FH0dX"
      }
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "source": [
        "# Installation of MemTorch (with CUDA functionality) using pip\n",
        "!pip install memtorch"
      ],
      "outputs": [],
      "metadata": {
        "id": "83ToE85BH0dY"
      }
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "source": [
        "# Installation of MemTorch (without CUDA functionality) using pip\n",
        "!pip install memtorch-cpu"
      ],
      "outputs": [],
      "metadata": {
        "id": "g49HrYnhH0dY"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "## 2. Training and Benchmarking a Deep Neural Network Using MNIST"
      ],
      "metadata": {
        "id": "qBYEsnSav5E1"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "MemTorch can currently be used to simulate the inference routines of MDNNs. Consequently, prior to conversion, DNNs must be either defined and trained using PyTorch or imported using PyTorch. \n",
        "\n",
        "In this tutorial, a simple DNN architecture is trained and benchmarked using the MNIST hand-written character classification data set. \n",
        "\n",
        "* The MNIST data set consists of 70,000 28x28 greyscale images in 10 balanced classes, representing the numbers 0-9. There are 60,000 training images and 10,000 test images. \n",
        "* In the cell below, a DNN is trained for 10 epochs with a batch size of $\\Im=256$. \n",
        "* An initial learning rate of $\\eta = 1e-1$ is used, which is decayed by an order of magnitude after 5 training epochs. \n",
        "* Adam is used to optimize network parameters and Cross Entropy (CE) is used to determine network losses.\n",
        "* `memtorch.utils.LoadMNIST` is used to load the MNIST training and test sets. After each epoch, the model is evaluated using the MNIST test set. \n",
        "* The model that achieves the highest test set accuracy is saved as *trained_model.pt*."
      ],
      "metadata": {
        "id": "HCy9QqPHv5E3"
      }
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "source": [
        "import torch\n",
        "from torch.autograd import Variable\n",
        "import memtorch\n",
        "import torch.nn as nn\n",
        "import torch.nn.functional as F\n",
        "import torch.optim as optim\n",
        "from memtorch.utils import LoadMNIST\n",
        "import numpy as np\n",
        "\n",
        "class Net(nn.Module):\n",
        "    def __init__(self):\n",
        "        super(Net, self).__init__()\n",
        "        self.conv1 = nn.Conv2d(1, 20, 5, 1)\n",
        "        self.conv2 = nn.Conv2d(20, 50, 5, 1)\n",
        "        self.fc1 = nn.Linear(4*4*50, 500)\n",
        "        self.fc2 = nn.Linear(500, 10)\n",
        "\n",
        "    def forward(self, x):\n",
        "        x = F.relu(self.conv1(x))\n",
        "        x = F.max_pool2d(x, 2, 2)\n",
        "        x = F.relu(self.conv2(x))\n",
        "        x = F.max_pool2d(x, 2, 2)\n",
        "        x = x.view(-1, 4*4*50)\n",
        "        x = F.relu(self.fc1(x))\n",
        "        x = self.fc2(x)\n",
        "        return x\n",
        "\n",
        "def test(model, test_loader):\n",
        "    correct = 0\n",
        "    for batch_idx, (data, target) in enumerate(test_loader):        \n",
        "        output = model(data.to(device))\n",
        "        pred = output.data.max(1)[1]\n",
        "        correct += pred.eq(target.to(device).data.view_as(pred)).cpu().sum()\n",
        "\n",
        "    return 100. * float(correct) / float(len(test_loader.dataset))\n",
        "\n",
        "device = torch.device('cpu' if 'cpu' in memtorch.__version__ else 'cuda')\n",
        "epochs = 10\n",
        "learning_rate = 1e-1\n",
        "step_lr = 5\n",
        "batch_size = 256\n",
        "train_loader, validation_loader, test_loader = LoadMNIST(batch_size=batch_size, validation=False)\n",
        "model = Net().to(device)\n",
        "criterion = nn.CrossEntropyLoss()\n",
        "optimizer = optim.Adam(model.parameters(), lr=learning_rate)\n",
        "best_accuracy = 0\n",
        "for epoch in range(0, epochs):\n",
        "    print('Epoch: [%d]\\t\\t' % (epoch + 1), end='')\n",
        "    if epoch % step_lr == 0:\n",
        "        learning_rate = learning_rate * 0.1\n",
        "        for param_group in optimizer.param_groups:\n",
        "            param_group['lr'] = learning_rate\n",
        "\n",
        "    model.train()\n",
        "    for batch_idx, (data, target) in enumerate(train_loader):\n",
        "        optimizer.zero_grad()\n",
        "        output = model(data.to(device))\n",
        "        loss = criterion(output, target.to(device))\n",
        "        loss.backward()\n",
        "        optimizer.step()\n",
        "\n",
        "    accuracy = test(model, test_loader)\n",
        "    print('%2.2f%%' % accuracy)\n",
        "    if accuracy > best_accuracy:\n",
        "        torch.save(model.state_dict(), 'trained_model.pt')\n",
        "        best_accuracy = accuracy"
      ],
      "outputs": [],
      "metadata": {
        "id": "jVH_tu3tv5E4"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "## 3. Conversion of a Deep Neural Network to a Memristive Deep Neural Network "
      ],
      "metadata": {
        "id": "Ag8Z6Rn_v5E8"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "Within MemTorch, `memtorch.mn.Module.patch_model` can be used to convert DNNs to a MDNNs. Prior to conversion, a memristive device model must be defined and characterized in part (prior to the introduction of other non-ideal device characteristics).\n",
        "\n",
        "In the cell below:\n",
        "* A reference (base) memristor model from `memtorch.bh.memristor` is defined.\n",
        "* Optional reference memristor keyword arguments are set.\n",
        "* A `memtorch.bh.memristor.Memristor` object is instantiated\n",
        "* The hysteresis loop of the instantiated memristor object is generated/plotted.\n",
        "* The bipolar switching behaviour of the instantiated memristor object is generated/plotted."
      ],
      "metadata": {
        "id": "lyt9qHvAv5E9"
      }
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "source": [
        "reference_memristor = memtorch.bh.memristor.VTEAM\n",
        "reference_memristor_params = {'time_series_resolution': 1e-10}\n",
        "memristor = reference_memristor(**reference_memristor_params)\n",
        "memristor.plot_hysteresis_loop()\n",
        "memristor.plot_bipolar_switching_behaviour()"
      ],
      "outputs": [],
      "metadata": {
        "id": "dRMGKP-lv5E-"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "In the cell below, the trained DNN from Section 2 is converted to an equivalent MDNN, where all convolutional layers are replaced with memristive-equivalent layers. While only *Conv2d* layers are converted for demonstration purposes, we note that MemTorch currently supports conversion of *Conv1d*, *Conv2d*, *Conv3d*, and *Linear* layers. Specifically:\n",
        "* `memtorch.bh.map.Parameter.naive_map` is used to convert the weights within all `torch.nn.Conv2d` layers to equivalent conductance values, to be programmed to the two memristive devices used to represent each weight (positive and negative, respectively). \n",
        "* `tile_shape` is set to (128, 128), so that modular crossbar tiles of size 128x128 are used to represent weights. \n",
        "* `ADC_resolution` is set to 8 to set the bit width of all emulated Analogue to Digital Converters (ADC).\n",
        "* `ADC_overflow` is used to set the initial overflow rate of each ADC. \n",
        "* `quant_method` is used to set the quantization method used (linear, by default).\n",
        "* `transistor` is set to `True`, so a 1T1R arrangement is simulated. \n",
        "* `programming_routine` is set to `None` to skip device-level simulation of the programming routine. \n",
        "\n",
        "\n",
        "\n",
        "We note if `transistor` is `False` `programming_routine` must not be `None`. In which case, device-level simulation is performed for each device using `memtorch.bh.crossbar.gen_programming_signal` and `memtorch.bh.memristor.Memristor.simulate`, which use finite differences to model internal device dynamics. As `scheme` is not defined, a double-column parameter representation scheme is adopted. Finally, `max_input_voltage` is 0.3, meaning inputs to each layer are encoded between -0.3V and +0.3V."
      ],
      "metadata": {
        "id": "b0kLErl0v5FC"
      }
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "source": [
        "import copy\n",
        "from memtorch.mn.Module import patch_model\n",
        "from memtorch.map.Input import naive_scale\n",
        "from memtorch.map.Parameter import naive_map\n",
        "\n",
        "\n",
        "model = Net().to(device)\n",
        "model.load_state_dict(torch.load('trained_model.pt'), strict=False)\n",
        "patched_model = patch_model(copy.deepcopy(model),\n",
        "                          memristor_model=reference_memristor,\n",
        "                          memristor_model_params=reference_memristor_params,\n",
        "                          module_parameters_to_patch=[torch.nn.Conv2d],\n",
        "                          mapping_routine=naive_map,\n",
        "                          transistor=True,\n",
        "                          programming_routine=None,\n",
        "                          tile_shape=(128, 128),\n",
        "                          max_input_voltage=0.3,\n",
        "                          scaling_routine=naive_scale,\n",
        "                          ADC_resolution=8,\n",
        "                          ADC_overflow_rate=0.,\n",
        "                          quant_method='linear')"
      ],
      "outputs": [],
      "metadata": {
        "id": "oJWSTW5Qv5FD"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "In the cell below, all patched `torch.nn.Conv2d` layers are tuned using linear regression. A randomly generated tensor of size (8, `self.in_channels`, 32, 32) is propagated through each memristive layer and each legacy layer (accessible using `layer.forward_legacy`). `sklearn.linear_model.LinearRegression` is used to determine the coefficient and intercept between the linear relationship of each set of outputs, which is used to define the `transform_output` lamdba function, that maps the output of each layer to their equivalent representations."
      ],
      "metadata": {
        "id": "5WeMLQXBH0de"
      }
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "source": [
        "patched_model.tune_()"
      ],
      "outputs": [],
      "metadata": {
        "id": "Mam3ggffv5FG"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "Finally, in the cell below, the converted and tuned MDNN is benchmarked using the MNIST test data set. \n",
        "*Note: This cell may take a considerable amount of time to run.*"
      ],
      "metadata": {
        "id": "YN_U95V_H0de"
      }
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "source": [
        "print(test(patched_model, test_loader))"
      ],
      "outputs": [],
      "metadata": {
        "id": "U5F0muXPv5FK"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "## 4. Modeling Non-Ideal Device Characteristics"
      ],
      "metadata": {
        "id": "eV8IJSH6v5FN"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "\n",
        "Non-ideal device characteristics can either be encapsulated within device specific memristive models, or introduced to base (generic) models after conversion, using `memtorch.bh.nonideality.NonIdeality.apply_nonidealities`. Currently, the following non-ideal device characteristics are supported:\n",
        "* `memtorch.bh.nonideality.DeviceFaults`\n",
        "* `memtorch.bh.nonideality.Endurance` and `memtorch.bh.nonideality.Retention`\n",
        "* `memtorch.bh.nonideality.FiniteConductanceStates`\n",
        "* `memtorch.bh.nonideality.NonLinear`\n",
        "\n",
        "Stochastic parameters, used to model process variances, can be defined using `memtorch.bh.StochaticParameter`. The introduction of each type of non ideal device characteristic is demonstrated below.\n"
      ],
      "metadata": {
        "id": "R4H9f4d248V7"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "### 4.1 Modeling Device Faults\n",
        "\n",
        "Memristive devices are susceptible to failure, by either failing to eletroform at a pristine state, or becoming stuck at high or low resistance states. MemTorch incorporates a specific function for accounting for device failure, `memtorch.bh.nonideality.DeviceFaults`. \n",
        "\n",
        "In the cell below:\n",
        "* The original patched model is copied using `copy.deepcopy`.\n",
        "* `lrs_proportion` is set to 0.25, so that 25% of devices are assumed to fail to a low resistance state.\n",
        "* `hrs_proportion` is set to 0.10, so that 15% of devices are assumed to fail to a high resistance state.\n",
        "\n",
        "It is assumed that the total proportion of devices set to a high resistance state is equal to the proportion of devices that fail to eletroform at pristine states plus the proportion of devices stuck at a high resistance state.\n",
        "\n"
      ],
      "metadata": {
        "id": "pewV5scsH2ZT"
      }
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "source": [
        "from memtorch.bh.nonideality.NonIdeality import apply_nonidealities\n",
        "\n",
        "patched_model_ = apply_nonidealities(copy.deepcopy(patched_model),\n",
        "                                  non_idealities=[memtorch.bh.nonideality.NonIdeality.DeviceFaults],\n",
        "                                  lrs_proportion=0.25,\n",
        "                                  hrs_proportion=0.10,\n",
        "                                  electroform_proportion=0)"
      ],
      "outputs": [],
      "metadata": {
        "id": "WEJmY_ENH0dg"
      }
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "source": [
        "print(test(patched_model_, test_loader))"
      ],
      "outputs": [],
      "metadata": {
        "id": "KTxU1vckH0dg"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "### 4.2 Modeling Device Endurance and Retention\n",
        "\n",
        "Memristive devices possess non-ideal endurance and retention properties, which should be accounted for. MemTorch incorporates specific functions for accounting for device endurance and retention characteristics, `memtorch.bh.nonideality.Endurance`, and `memtorch.bh.nonideality.Retention`, respectively.\n",
        "\n",
        "All endurance and retention models are defined in `memtorch.bh.nonideality.endurance_retention_models`.\n",
        "\n",
        "In the cell below:\n",
        "* The original patched model is copied using `copy.deepcopy`.\n",
        "* `x`, the number of SET-RESET cycles is set to be equal to 10,000.\n",
        "* Endurance characteristics are accounted for using `memtorch.bh.nonideality.NonIdeality.Endurance` and `memtorch.bh.nonideality.endurance_retention_models.model_endurance_retention`.\n",
        "* `operation_mode` within `endurance_model_kwargs` is set to `sudden`, so that sudden failure is modeled, and various other model arguments are set.\n"
      ],
      "metadata": {
        "id": "D2Vcpuuw5D_S"
      }
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "source": [
        "from memtorch.bh.nonideality.NonIdeality import apply_nonidealities\n",
        "\n",
        "patched_model_ = apply_nonidealities(copy.deepcopy(patched_model),\n",
        "                                  non_idealities=[memtorch.bh.nonideality.NonIdeality.Endurance],\n",
        "                                  x=1e4,\n",
        "                                  endurance_model=memtorch.bh.nonideality.endurance_retention_models.model_endurance_retention,\n",
        "                                  endurance_model_kwargs={\n",
        "                                        \"operation_mode\": memtorch.bh.nonideality.endurance_retention_models.OperationMode.sudden,\n",
        "                                        \"p_lrs\": [1, 0, 0, 0],\n",
        "                                        \"stable_resistance_lrs\": 100,\n",
        "                                        \"p_hrs\": [1, 0, 0, 0],\n",
        "                                        \"stable_resistance_hrs\": 1000,\n",
        "                                        \"cell_size\": 10,\n",
        "                                        \"temperature\": 350,\n",
        "                                  })"
      ],
      "outputs": [],
      "metadata": {
        "id": "4thsaVDEv5FS"
      }
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "source": [
        "print(test(patched_model_, test_loader))"
      ],
      "outputs": [],
      "metadata": {
        "id": "fl7_hStXH0dh"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "In the cell below:\n",
        "* The original patched model is copied using `copy.deepcopy`.\n",
        "* `time`, the retention time, is set to be equal to 1,000s.\n",
        "* Retention characteristics are accounted for using `memtorch.bh.nonideality.NonIdeality.Retention` and `memtorch.bh.nonideality.endurance_retention_models.model_conductance_drift`.\n",
        "* `initial_time` within `retention_model_kwargs`, the initial time, is set to be equal to 1s.\n",
        "* `drift_coefficient` within `retention_model_kwargs` is set to be equal to 0.1."
      ],
      "metadata": {
        "id": "KykmIom1H0dh"
      }
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "source": [
        "from memtorch.bh.nonideality.NonIdeality import apply_nonidealities\n",
        "\n",
        "patched_model_ = apply_nonidealities(copy.deepcopy(patched_model),\n",
        "                                  non_idealities=[memtorch.bh.nonideality.NonIdeality.Retention],\n",
        "                                  time=1e3,\n",
        "                                  retention_model=memtorch.bh.nonideality.endurance_retention_models.model_conductance_drift,\n",
        "                                  retention_model_kwargs={\n",
        "                                        \"initial_time\": 1,\n",
        "                                        \"drift_coefficient\": 0.1,\n",
        "                                  })"
      ],
      "outputs": [],
      "metadata": {
        "id": "jTNNAcCiH0dh"
      }
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "source": [
        "print(test(patched_model_, test_loader))"
      ],
      "outputs": [],
      "metadata": {
        "id": "gV1ANo6-H0di"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "### 4.3 Modeling a Finite Number of Conductance States\n",
        "\n",
        "Realistic memristive devices are non-ideal and have a finite number of stable discrete electrically switchable conductance states, bounded by a low conductance semiconducting state, and a high-conductance metallic state. MemTorch incorporates a specific function for accounting for devices with a finite number of conductance states, `memtorch.bh.nonideality.FiniteConductanceStates`. \n",
        "\n",
        "In the cell below:\n",
        "* The original patched model is copied using `copy.deepcopy`.\n",
        "* A finite number of conductance states are accounted for using `memtorch.bh.nonideality.NonIdeality.FiniteConductanceStates`.\n",
        "* `conductance_states` is set to be equal to 5, to model 5 evenly-distributed conductance states."
      ],
      "metadata": {
        "id": "rR03gyJVH0di"
      }
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "source": [
        "from memtorch.bh.nonideality.NonIdeality import apply_nonidealities\n",
        "\n",
        "patched_model_ = apply_nonidealities(copy.deepcopy(patched_model),\n",
        "                                  non_idealities=[memtorch.bh.nonideality.NonIdeality.FiniteConductanceStates],\n",
        "                                  conductance_states=5)            "
      ],
      "outputs": [],
      "metadata": {
        "id": "UH0e7AtWH0di"
      }
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "source": [
        "print(test(patched_model_, test_loader))"
      ],
      "outputs": [],
      "metadata": {
        "id": "zA6Bc-PPH0di"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "### 4.4 Modeling Non-Linear Device Characteristics\n",
        "\n",
        "Non-ideal memristive devices have non-linear I/V device characteristics, especially at high voltages, which are difficult to accurately and efficiently model. The `memtorch.bh.nonideality.NonLinear.apply_non_linear` function can be used to efficiently model non-linear device I/V characteristics during inference for devices with an infinite number of discrete conductance states, and for devices with a finite number of conductance states. \n",
        "\n",
        "For cases where devices are not simulated using their internal dynamics, it is assumed that the change in conductance during read cycles is negligible.\n",
        "\n",
        "Within MemTorch, `memtorch.bh.nonideality.NonLinear.apply_non_linear` uses two methods to effectively model non-linear device I/V characteristics:\n",
        "\n",
        "1. During inference, each device is simulated for timesteps of duration `device.time_series_resolution` using `device.simulate`.\n",
        "2. Post weight mapping and programming, the I/V characteristics of each device are determined using a single reset voltage sweep.\n",
        "\n",
        "In the cell below:\n",
        "* The original patched model is copied using `copy.deepcopy`.\n",
        "* Non-linear device characteristics are accounted for using `memtorch.bh.nonideality.NonLinear`.\n",
        "* `simulate` is set to be equal to `True`, so during inference each device is simulated.\n",
        "\n",
        "\n"
      ],
      "metadata": {
        "id": "7FBLj9-mH0di"
      }
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "source": [
        "from memtorch.bh.nonideality.NonIdeality import apply_nonidealities\n",
        "\n",
        "patched_model_ = apply_nonidealities(copy.deepcopy(patched_model),\n",
        "                                  non_idealities=[memtorch.bh.nonideality.NonIdeality.NonLinear],\n",
        "                                  simulate=True)"
      ],
      "outputs": [],
      "metadata": {
        "id": "F-0leLIdH0di"
      }
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "source": [
        "print(test(patched_model_, test_loader))"
      ],
      "outputs": [],
      "metadata": {
        "id": "-AWwwouiH0dj"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "In the cell below:\n",
        "* The original patched model is copied using `copy.deepcopy`.\n",
        "* Non-linear device characteristics are accounted for using `memtorch.bh.nonideality.NonLinear`.\n",
        "* `simulate` is not set, so the I/V characteristics of each device are determined using a single reset voltage sweep.\n",
        "* `sweep_duration` is set to be equal to 2s.\n",
        "* `sweep_voltage_signal_amplitude` is set to be equal to 1V.\n",
        "* `sweep_voltage_signal_frequency` is set to be equal to 0.5Hz.\n"
      ],
      "metadata": {
        "id": "F2W039iPH0dj"
      }
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "source": [
        "from memtorch.bh.nonideality.NonIdeality import apply_nonidealities\n",
        "\n",
        "patched_model_ = apply_nonidealities(copy.deepcopy(patched_model),\n",
        "                                  non_idealities=[memtorch.bh.nonideality.NonIdeality.NonLinear],\n",
        "                                  sweep_duration=2,\n",
        "                                  sweep_voltage_signal_amplitude=1,\n",
        "                                  sweep_voltage_signal_frequency=0.5)"
      ],
      "outputs": [],
      "metadata": {
        "id": "SZOh4WJZH0dj"
      }
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "source": [
        "print(test(patched_model_, test_loader))"
      ],
      "outputs": [],
      "metadata": {
        "id": "SnPpuvPaH0dj"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "### 4.5 Modeling Stochastic Parameters"
      ],
      "metadata": {
        "id": "zTA0KyOEH0dj"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "MemTorch supports the usage of stochastic parameters for higher flexibility to simply account for process variances using `memtorch.bh.StochasticParameter.StochasticParameter`. Stochastic parameters can be used when defining device characteristics. \n",
        "\n",
        "In the cell below:\n",
        "* A memristor object is characterised using stochastic parameters defining low and high resistance states.\n",
        "* The memristor object is instantiated, and the hysteresis loop and bipolar switching behaviour of the instantiated memristor object is generated/plotted.\n",
        "\n",
        "Each time the memristor object is instantiated, stochastic parameters will be resampled.\n"
      ],
      "metadata": {
        "id": "HoS7ZHhdH0dk"
      }
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "source": [
        "import memtorch\n",
        "\n",
        "reference_memristor = memtorch.bh.memristor.VTEAM\n",
        "reference_memristor_params = {'time_series_resolution': 1e-10, \n",
        "                              'r_off': memtorch.bh.StochasticParameter(loc=1000, scale=200, min=2),\n",
        "                              'r_on': memtorch.bh.StochasticParameter(loc=5000, scale=sigma, min=1)}\n",
        "\n",
        "memristor = reference_memristor(**reference_memristor_params)\n",
        "memristor.plot_hysteresis_loop()\n",
        "memristor.plot_bipolar_switching_behaviour()"
      ],
      "outputs": [],
      "metadata": {
        "id": "i2Kozl3dH0dk"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "## Final Remarks\n",
        "A complete API is avaliable [here](https://memtorch.readthedocs.io/). To learn how to use MemTorch, and to reproduce results of ‘_MemTorch: An Open-source Simulation Framework for Memristive Deep Learning Systems_’, we provide numerous tutorials in the form of Jupyter notebooks [here](https://memtorch.readthedocs.io/en/latest/tutorials.html).\n",
        "\n",
        "Current issues, feature requests and improvements are welcome, and are tracked using: https://github.com/coreylammie/MemTorch/projects/1. These should be reported [here](https://github.com/coreylammie/MemTorch/issues)."
      ],
      "metadata": {
        "id": "MSOaAOQAH0dk"
      }
    }
  ],
  "metadata": {
    "colab": {
      "collapsed_sections": [],
      "name": "Tutorial.ipynb",
      "provenance": []
    },
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.8.5"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}